#!/bin/env python3
# -*- encoding=utf-8 -*-
"""
# **********************************************************************************
# Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
# [openeuler-jenkins] is licensed under the Mulan PSL v1.
# You can use this software according to the terms and conditions of the Mulan PSL v1.
# You may obtain a copy of Mulan PSL v1 at:
#     http://license.coscl.org.cn/MulanPSL
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v1 for more details.
# Author: wangge
# Create: 2021-10-29
# **********************************************************************************
"""
import os
import configparser
import linecache
import time
import subprocess
import sqlite3
from libs.log.logger import log_check
from main.common.executecmd import ExecuteCmd

HISTORY_LOG_PATH = '/var/tmp/li-wen/getbuildtime_history.log'
PROJ_STATUS_RES_PATH = '/var/tmp/li-wen/projectstatus_result.log'
ARCH_STATUS_RES_PATH = '/var/tmp/li-wen/projectststus_architecture.log'
X86_TMP_STATUS_PATH = '/var/tmp/li-wen/x86_temp_status.log'
DATABASE_PATH = os.path.dirname(os.path.realpath(__file__)) + '/buildtime_cache.db'

GETBUILDTIME_CHECK_SHELL = 'getbuildtime_check_param'
PROJECTSTATUS_CHECK_SHELL = 'projectstatus_check_param'
LEVELSTATISTIC_CHECK_SHELL = 'levelstatistic_check_param'

EXECMD = ExecuteCmd()

class QueryProject(object):
    """
    query project information such as:
    1. building time of one package
    2. package amount in specified status
    3. package level distribution in specified status
    """
    def get_parent_path(self):
        """
        get check shell parent path
        """
        currpath = os.path.dirname(os.path.realpath(__file__))
        path_list = currpath.split('/')
        if not 'li-wen' in path_list:
            return None
        proj_index = path_list.index('li-wen')
        parentpath = '/'
        for index in range(1, proj_index + 1):
            parentpath = os.path.join(parentpath, path_list[index])
        parentpath = os.path.join(parentpath, 'libs', 'api')
        return parentpath

    def check_shell(self, shellname):
        """
        check shell getbuildtime_check_param
        """
        parentpath = self.get_parent_path()
        currfile = os.path.join(parentpath, shellname)
        return os.path.exists(currfile)

    def check_param(self, shellname, paramstring):
        """
        check project validity
        param shellname:
        param paramstring:
        """
        path = self.get_parent_path()
        cmd = path + '/' + shellname + ' ' + paramstring
        valid = EXECMD.cmd_output(cmd.split())
        if shellname in valid:
            log_check.error(f'{valid}')
            return False
        else:
            return True

    def check_status(self, status):
        """
        check validation of stauts
        param status:
        """
        if not status in ['scheduled', 'dispatch', 'building', \
                          'signing', 'finished', 'succeeded', 'failed', \
                          'unresolvable', 'blocked', 'excluded']:
            log_check.error(f'invalid status')
            return False
        return True

    def repo_to_arch(self, repo):
        """
        transform repository to architecture
        param repo:
        """
        if repo == 'standard_aarch64':
            return 'aarch64'
        elif repo == 'standard_x86_64':
            return 'x86_64'
        else:
            return None

    def gen_duration_list(self, project, repo, package, boundary):
        """
        generate package build duration list
        param project:
        param arch:
        param package:
        param limit:
        """
        duration_list = []
        query_item = eval(boundary)
        #translate repository to architecture
        arch = self.repo_to_arch(repo)
        if arch is None:
            log_check.error(f'{repo} is not a valid repository value')
            #empty list is a reasonable return value
            return duration_list
        #get packge job history save it to history.log
        cmd = 'osc api -X GET /build/' + project + '/' + repo + '/' + arch \
              + '/_jobhistory?package=' + package
        history_content = EXECMD.cmd_output(cmd.split())
        with open(HISTORY_LOG_PATH, 'w+') as history_log_handle:
            history_log_handle.write(history_content)
        #delete failed item
        cmd = 'sed -i ' + '/failed/d' + ' ' + HISTORY_LOG_PATH
        EXECMD.cmd_status(cmd.split())
        #delete invalid item
        cmd = 'sed -i ' + '/jobhistlist/d' + ' ' \
              + HISTORY_LOG_PATH
        EXECMD.cmd_status(cmd.split())
        cmd_list = ['sed', '-i', '/SSL certificate checks disable/d',HISTORY_LOG_PATH] 
        EXECMD.cmd_status(cmd_list)
        cmd = 'sed -i ' + '1d' + ' ' + HISTORY_LOG_PATH
        EXECMD.cmd_status(cmd.split())
        #get valid item num
        with open(HISTORY_LOG_PATH, 'r') as rd_handle:
            valid_boundary = len(rd_handle.readlines())
            if valid_boundary < query_item:
                query_item = valid_boundary
        #generate versrel and bcnt list
        linecache.clearcache()
        for index in range(valid_boundary, valid_boundary - query_item, -1):
            line_content = linecache.getline(HISTORY_LOG_PATH, index).strip()
            part_list = line_content.split(' ')
            list_len = len(part_list)
            if list_len == 17:
                endtime = eval(part_list[8][9:-1])
                starttime = eval(part_list[7][11: -1])
                duration = endtime - starttime
                duration_list.append(duration)
        return duration_list

    def query_history_build_time_of_package(self, project, repo, package, boundary):
        """
        query specified package's history build time
        param project:
        param repo:
        param package:
        param boundary:
        return: None if failed
        """
        time_dict = {"average": 0, "history":[]}
        #check tmp file directory
        if not os.path.exists(os.path.dirname(HISTORY_LOG_PATH)):
            os.makedirs(os.path.dirname(HISTORY_LOG_PATH))
        #check existion of getbuildtime_check_param
        shell = self.check_shell(GETBUILDTIME_CHECK_SHELL)
        if not shell:
            log_check.error(f'getbuildtime_check_param is not exist in ../../libs/api!')
            return None
        #check parameters
        param_str = project + ' ' + repo + ' '+ package + ' ' + boundary
        valid = self.check_param(GETBUILDTIME_CHECK_SHELL, param_str)
        if not valid:
            return None
        #calc evaluation duration
        duration_list = self.gen_duration_list(project, repo, package, boundary)
        list_len = len(duration_list)
        if list_len == 0:
            log_check.error(f'{project}:{repo}:{package} generate duration list failed')
            return None
        duration_sum = sum(duration_list)
        duration_ever = duration_sum/list_len
        time_dict["average"] = duration_ever
        time_dict["history"] = duration_list
        return time_dict


    def gen_arch_result_file(self, project, repo):
        """
        generate obs result file for specified project and repository
        param project:
        param repo:
        """
        #generate result file for project
        cmd = 'osc api -X GET /build/' + project + '/_result'
        status_content = EXECMD.cmd_output(cmd.split())
        with open(PROJ_STATUS_RES_PATH, 'w+') as status_file_handle:
            status_file_handle.write(status_content)
        #check existion for file result.log
        if not os.path.exists(PROJ_STATUS_RES_PATH):
            log_check.error(f'projectstatus_result.log file generation failed!')
            return False
        #get startline for each architecture in result.log
        cmd = 'sed -n -e /standard_x86_64/= ' + PROJ_STATUS_RES_PATH
        x86start = EXECMD.cmd_output(cmd.split()).strip()
        cmd = 'sed -n -e /standard_aarch64/= ' + PROJ_STATUS_RES_PATH
        armstart = EXECMD.cmd_output(cmd.split()).strip()
        #generate specified architecture result file
        if repo == 'standard_aarch64':
            cmd = 'tail -n +' + str(eval(armstart)+1) + ' ' + PROJ_STATUS_RES_PATH
            repo_status_content = EXECMD.cmd_output(cmd.split())
        elif repo == 'standard_x86_64':
            cmd = 'head -n ' + str(eval(armstart)-1) + ' ' + PROJ_STATUS_RES_PATH
            repo_status_content = EXECMD.cmd_output(cmd.split())
            with open(X86_TMP_STATUS_PATH, 'w+') as x86_tmp_handle:
                x86_tmp_handle.write(repo_status_content)
            cmd = 'tail -n +' + str(eval(x86start)+1) + ' ' + X86_TMP_STATUS_PATH
            repo_status_content = EXECMD.cmd_output(cmd.split())
        else:
            log_check.error(f'invalid repository')
            return False
        with open(ARCH_STATUS_RES_PATH, 'w') as repo_status_handle:
            repo_status_handle.write(repo_status_content)
        if not os.path.exists(ARCH_STATUS_RES_PATH):
            log_check.error(f'projectstatus_architecture.log generation failed')
            return False
        return True

    def get_package_list(self, status):
        """
        get package name list in specified status
        param status:
        """
        #existion of architecture.log should be ensured by invoker
        package_list = []
        #get package status file in specified status
        feature_str = '/code="' + status + '"/!d'
        subprocess.Popen(['sed','-i', feature_str, ARCH_STATUS_RES_PATH])
        #check package amount
        time.sleep(1)
        with open(ARCH_STATUS_RES_PATH, 'r') as rd_handle:
            package_amount = len(rd_handle.readlines())
            if package_amount == 0:
                rd_handle.close()
                #empty list is a reasonalbe return value,that mean no package in specfied status
                return package_list
        #fill package_list
        for index in range(1, package_amount + 1, 1):
            line_content = linecache.getline(ARCH_STATUS_RES_PATH, index).strip()
            linecache.clearcache()
            line_content_split = line_content.split(' ')
            if 'package' in line_content_split[1]:
                package_list.append(line_content_split[1][9:-1])
        return package_list 


    def query_status_num_of_project(self, project, repo, status):
        """
        query specified project, get amount and package list in specified status
        param project:
        param repo:
        param status:
        return: stauts_ditt["packages"][0] == 'fault' if failed
        """
        status_dict = {"total": 0, "packages": ['fault']}
        #check tmp file directory
        if not os.path.exists(os.path.dirname(PROJ_STATUS_RES_PATH)):
            os.makedirs(os.path.dirname(PROJ_STATUS_RES_PATH))
        #check existion of projectstatus_check_param
        shell = self.check_shell(PROJECTSTATUS_CHECK_SHELL)
        if not shell:
            log_check.error(f'projectstatus_check_param is not exist in ../../libs/api!')
            return status_dict
        #check parameters
        param_str = project + ' ' + repo + ' ' + status
        valid = self.check_param(PROJECTSTATUS_CHECK_SHELL, param_str)
        if not valid:
            return status_dict
        #generate resutl file in specified architecture
        generate_res = self.gen_arch_result_file(project, repo)
        if not generate_res:
            return status_dict
        packages = self.get_package_list(status)
        status_dict["total"] = len(packages)
        status_dict["packages"] = packages
        return status_dict


    def gen_status_package_list(self, project, status):
        """
        generate package list in specified status
        param project:
        param status:
        return: status_packages['architecture']["packages"][0] == 'fault' if failed
        """
        status_packages = {'x86': {}, 'arm': {}}
        x86_packages = self.query_status_num_of_project(project, 'standard_x86_64', status)
        if (len(x86_packages["packages"]) != 0) and (x86_packages["packages"][0] == 'fault'):
            log_check.error(f'{project}:x86:get {status} package failed')
            return None
        status_packages['x86'] = x86_packages
        arm_packages = self.query_status_num_of_project(project, 'standard_aarch64',status)
        if (len(arm_packages["packages"]) != 0) and (arm_packages["packages"][0] == 'fault'):
            log_check.error(f'{project}:aarch64:get {status} package failed')
            return None
        status_packages['arm'] = arm_packages
        return status_packages

    def get_level_from_db(self, project, repository, package):
        """
        get package build level from database
        param project:
        param repository:
        param package:
        """
        dbcon = sqlite3.connect(DATABASE_PATH)
        cursor = dbcon.cursor()
        query_cmd = 'SELECT level FROM buildtime WHERE project=\'' \
                    + 'openEuler:Mainline' + '\' and repository=\'' + repository \
                    + '\' and package=\'' + package + '\''
        ret_list = cursor.execute(query_cmd).fetchone()
        cursor.close()
        dbcon.close()
        if not ret_list is None:
            return ret_list[0]
        else:
            log_check.error(f'{project}:{repository}:{package} build level invalid')
            return None
        
    def gen_package_amount_in_each_level(self, project, status):
        """
        get specified status package amount in each level
        param project:
        param status:
        """
        status_level_list = []
        arm_level_dict = {"aarch64": {"l1": 0, "l2":0, "l3":0}}
        x86_level_dict = {"x86": {"l1": 0, "l2":0, "l3":0}}
        arm_l1_num = 0
        arm_l2_num = 0
        arm_l3_num = 0
        x86_l1_num = 0
        x86_l2_num = 0
        x86_l3_num = 0
        #check existion of levelstatistic_check_param
        shell = self.check_shell(LEVELSTATISTIC_CHECK_SHELL)
        if not shell:
            log_check.error(f'levelstatistic_check_param is not exists in../../libs/api!')
            return status_level_list
        #check project
        valid = self.check_param(LEVELSTATISTIC_CHECK_SHELL, project)
        if not valid:
            return status_level_list
        #check status
        valid = self.check_status(status)
        if not valid:
            log_check.error(f'invalid status')
            return status_level_list
        #get package list in specified status
        project_list = self.gen_status_package_list(project, status)
        if project_list is None:
            #erro log is recorded by gen_status_package_list, and empty list is an reasonable value for accumulation
            return status_level_list
        x86_package_list = project_list["x86"]["packages"]
        arm_package_list = project_list["arm"]["packages"]
        for x86_package in x86_package_list:
            level = self.get_level_from_db(project, 'standard_x86_64', x86_package)
            if level == 'l1':
                x86_l1_num = x86_l1_num + 1
            elif level == 'l2':
                x86_l2_num = x86_l2_num + 1
            elif level == 'l3':
                x86_l3_num = x86_l3_num + 1
            else:
                pass
        for arm_package in arm_package_list:
            level = self.get_level_from_db(project, 'standard_aarch64', arm_package)
            if level == 'l1':
                arm_l1_num = arm_l1_num + 1
            elif level=='l2':
                arm_l2_num = arm_l2_num + 1
            elif level=='l3':
                arm_l3_num = arm_l3_num + 1
            else:
                pass
        arm_level_dict["aarch64"]["l1"] = arm_l1_num
        arm_level_dict["aarch64"]["l2"] = arm_l2_num
        arm_level_dict["aarch64"]["l3"] = arm_l3_num
        x86_level_dict["x86"]["l1"] = x86_l1_num
        x86_level_dict["x86"]["l2"] = x86_l2_num
        x86_level_dict["x86"]["l3"] = x86_l3_num
        status_level_list.append(arm_level_dict)
        status_level_list.append(x86_level_dict)
        return status_level_list

    def status_statistics_add(self, events_list_1, events_list_2):
        """
        add status level statistics from different project
        param events_list_1:
        param events_list_2:
        """
        status_events_list=[{"aarch64":{"l1":0, "l2":0, "l3":0}}, {"x86":{"l1":0,"l2":0,"l3":0}}]
        status_events_list[0]["aarch64"]["l1"] = events_list_1[0]["aarch64"]["l1"] + events_list_2[0]["aarch64"]["l1"]
        status_events_list[0]["aarch64"]["l2"] = events_list_1[0]["aarch64"]["l2"] + events_list_2[0]["aarch64"]["l2"]
        status_events_list[0]["aarch64"]["l3"] = events_list_1[0]["aarch64"]["l3"] + events_list_2[0]["aarch64"]["l3"]
        status_events_list[1]["x86"]["l1"] = events_list_1[1]["x86"]["l1"] + events_list_2[1]["x86"]["l1"]
        status_events_list[1]["x86"]["l2"] = events_list_1[1]["x86"]["l2"] + events_list_2[1]["x86"]["l2"]
        status_events_list[1]["x86"]["l3"] = events_list_1[1]["x86"]["l3"] + events_list_2[1]["x86"]["l3"]
        return status_events_list

    def query_schedule_statistics(self, projectlist):
        """
        get package level statistics from specified project list
        param projectlist:
        """
        schedule_events_list=[{"aarch64":{"l1":0, "l2":0, "l3":0}}, {"x86":{"l1":0,"l2":0,"l3":0}}]
        for project in projectlist:
            single_evens_list = self.gen_package_amount_in_each_level(project, 'scheduled')
            if len(single_evens_list) != 0:
                schedule_events_list = self.status_statistics_add(schedule_events_list, single_evens_list)
        return schedule_events_list

